package fs

import (
	"notabug.org/apiote/next-eeze/crypto"
	"notabug.org/apiote/next-eeze/password"

	"io/ioutil"
	"log"
	"os"
	"os/user"
	"path/filepath"

	"git.sr.ht/~sircmpwn/go-bare"
)

type EncryptedContent struct {
	Salt       []byte
	CipherText []byte
}

type Credentials struct {
	Server   string
	Username string
	Password string
}

type FidoCredential struct {
	Cdh    []byte
	Salt   []byte
	CredID []byte
}

func getDataLocation() string {
	usr, _ := user.Current()
	dir := usr.HomeDir
	path := filepath.Join(dir, "/.local/share/next-eeze/")
	return path
}

// todo memguard passwords, masterPassword
func SaveBare(passwords []password.BarePassword, masterPassword string) error {
	salt := crypto.MakeSalt()
	// todo memguard
	key := crypto.DeriveKey(masterPassword, salt)
	result, err := os.Create(getDataLocation() + "/passwords.bare")
	if err != nil {
		log.Fatal("Error creating passwords file. ", err)
		return err
	}
	defer result.Close()
	// todo memguard
	bytes, err := bare.Marshal(&passwords)
	if err != nil {
		log.Fatal("Error marshalling passwords. ", err)
		return err
	}
	cipherText, err := crypto.Encrypt(bytes, key)
	if err != nil {
		log.Fatal("Error encrypting credentials. ", err)
		return err
	}

	enc := EncryptedContent{
		Salt:       salt,
		CipherText: cipherText,
	}
	bytes, err = bare.Marshal(&enc)
	if err != nil {
		log.Fatal("Error marshalling encrypted credentials. ", err)
		return err
	}
	_, err = result.Write(bytes)
	if err != nil {
		log.Fatal("Error writing to file. ", err)
		return err
	}
	_, err = result.Write(bytes)
	if err != nil {
		log.Fatal("Error writing to file. ", err)
		return err
	}
	return nil
}

// todo memguard passwords, masterPassword
func Save(passwords []password.NextPassword, masterPassword string) error {
	// todo memguard
	barePasswords := []password.BarePassword{}
	// todo memguard
	for _, p := range passwords {
		barePasswords = append(barePasswords, p.ToBarePassword())
	}
	err := SaveBare(barePasswords, masterPassword)
	return err
}

// todo memguard masterPassword
func Read(masterPassword string) ([]password.BarePassword, error) {
	// todo memguard
	passwords := []password.BarePassword{}
	f, err := os.Open(getDataLocation() + "/passwords.bare")
	if err != nil {
		log.Fatal("Error opening. ", err)
		return passwords, err
	}
	defer f.Close()
	r := bare.NewReader(f)
	salt, err :=  r.ReadData()
	if err != nil {
		log.Fatal("Error reading salt. ", err)
		return passwords, err
	}
	cipher, err :=  r.ReadData()
	if err != nil {
		log.Fatal("Error reading cipher. ", err)
		return passwords, err
	}
	// todo memguard
	key := crypto.DeriveKey(masterPassword, salt)
	// todo memguard
	plaintext, err := crypto.Decrypt(cipher, key)
	if err != nil {
		log.Fatal("Error decrypting passwords. ", err)
		return passwords, err
	}
	err = bare.Unmarshal(plaintext, &passwords)
	if err != nil {
		log.Fatal("Error unmarshalling. ", err)
		return nil, err
	}
	return passwords, nil
}

// todo memguard credentials, masterPassword
func SaveCredentials(credentials Credentials, masterPassword string) error {
	salt := crypto.MakeSalt()
	// todo memguard
	key := crypto.DeriveKey(masterPassword, salt)

	result, err := os.Create(getDataLocation() + "/credentials.bare")
	if err != nil {
		log.Fatal("Error creating credentials file. ", err)
		return err
	}
	defer result.Close()
	// todo memguard
	bytes, err := bare.Marshal(&credentials)
	if err != nil {
		log.Fatal("Error marshalling credentials. ", err)
		return err
	}
	cipherText, err := crypto.Encrypt(bytes, key)
	if err != nil {
		log.Fatal("Error encrypting credentials. ", err)
		return err
	}

	enc := EncryptedContent{
		Salt:       salt,
		CipherText: cipherText,
	}
	bytes, err = bare.Marshal(&enc)
	if err != nil {
		log.Fatal("Error marshalling encrypted credentials. ", err)
		return err
	}
	_, err = result.Write(bytes)
	if err != nil {
		log.Fatal("Error writing to file. ", err)
		return err
	}
	return nil
}

// todo memguard masterPassword
func ReadCredentials(masterPassword string) (Credentials, error) {
	enc := EncryptedContent{}
	// todo memguard
	credentials := Credentials{}
	content, err := ioutil.ReadFile(getDataLocation() + "/credentials.bare")
	if err != nil {
		log.Fatal("Error reading to file. ", err)
		return credentials, err
	}
	err = bare.Unmarshal(content, &enc)
	if err != nil {
		log.Fatal("Error unmarshalling encrypted. ", err)
		return credentials, err
	}
	// todo memguard
	key := crypto.DeriveKey(masterPassword, enc.Salt)
	// todo memguard
	plaintext, err := crypto.Decrypt(enc.CipherText, key)
	if err != nil {
		log.Fatal("Error decrypting credentials. ", err)
		return credentials, err
	}
	err = bare.Unmarshal(plaintext, &credentials)
	return credentials, nil
}

// todo memguards
func SaveFidoCredential(c FidoCredential) error {
	bytes, err := bare.Marshal(&c)
	if err != nil {
		log.Fatal("Error creating credentials file. ", err)
		return err
	}
	result, err := os.Create(getDataLocation() + "/fido.bare")
	if err != nil {
		log.Fatal("Error creating credentials file. ", err)
		return err
	}
	defer result.Close()
	_, err = result.Write(bytes)
	if err != nil {
		log.Fatal("Error writing to file. ", err)
		return err
	}
	return nil
}

// todo memguards
func ReadFidoCredential() (FidoCredential, error) {
	c := FidoCredential{}
	content, err := ioutil.ReadFile(getDataLocation() + "/fido.bare")
	if err != nil {
		log.Fatal("Error reading to file. ", err)
		return c, err
	}
	err = bare.Unmarshal(content, &c)
	if err != nil {
		log.Fatal("Error unmarshalling encrypted. ", err)
		return c, err
	}
	return c, nil
}

func RemoveFidoCredential() error {
	err := os.Remove(getDataLocation() + "/fido.bare")
	if err != nil && os.IsNotExist(err) {
		return nil
	} else {
		return err
	}
}

func IsFidoCredentialPresent() (bool, error) {
	_, err := os.Stat(getDataLocation() + "/fido.bare")
	if err != nil {
		if os.IsNotExist(err) {
			return false, nil
		} else {
			return false, err
		}
	}
	return true, nil
}
